use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::{Arc, Mutex};
use std::thread;
use std::thread::JoinHandle;

pub struct ThreadFactory {
    workers: Vec<Worker>,
    sender: Sender<MessageType>,
}
impl ThreadFactory {
    pub fn new(size: usize) -> ThreadFactory {
        let (sender, receiver) = channel();
        let arc = Arc::new(Mutex::new(receiver));
        let workers = (0..size).map(|id| {
            let receiver = Arc::clone(&arc);
             Worker::new(id, receiver).unwrap()
        }).collect();

        ThreadFactory { workers, sender }
    }
    // pub fn new(size: usize) -> Result<ThreadFactory, Box<dyn std::error::Error>> {
    //     let (sender, receiver) = channel();
    //     let arc_receiver = Arc::new(Mutex::new(receiver));
    //     let workers = (0..size).map(|id| {
    //         let arc_clone = Arc::clone(&arc_receiver);
    //         Worker::new(id, arc_clone)
    //     }).collect::<Result<Vec<_>, _>>()?;
    //
    //     Ok(ThreadFactory { workers, sender })
    // }
    pub fn exec<F>(&self, f: F)
    where
        F: FnOnce() + 'static + Send,
    {
        self.sender
            .send(MessageType::NewJob(Box::new(f)))
            .map_err(|e| eprintln!("Failed to send job: {}", e))
            .ok();
    }
}
impl Drop for ThreadFactory {
    fn drop(&mut self) {
        self.workers.iter().for_each(|_| {
            self.sender.send(MessageType::Terminal).unwrap();
        });
        for x in &mut self.workers {
            let option = x.thread.take();
            let _ = option.unwrap().join().unwrap_or_else(|e| eprintln!("Thread join error: {:?}", e));
        }
    }
}
struct Worker {
    id: usize,
    thread: Option<JoinHandle<()>>,
}
impl Worker {
    fn new(id: usize, receiver: Arc<Mutex<Receiver<MessageType>>>) -> Result<Self, Box<dyn std::error::Error>> {
        let thread = Some(thread::spawn(move || loop {
            let result = receiver.lock().unwrap().recv();
            match result {
                Ok(MessageType::NewJob(job)) => {
                    println!("[Thread {}] process task!", id);
                    job();
                }
                Ok(MessageType::Terminal) => {
                    println!("[Thread {}] terminal", id);
                    break;
                }
                Err(_) => {
                    println!("[Thread {}] receiver error", id);
                    break;
                }
            }
        }));
        Ok(Worker { id, thread })
    }
}

type Job = Box<dyn FnOnce() + 'static + Send>;
enum MessageType {
    NewJob(Job),
    Terminal,
}
